/*
 *  Video (VGA - text mode) Driver
*/

#include "video.h"
#include "util.h"


typedef unsigned size_t;

// pointer to VGA video memory
unsigned short *vidmem;


/*
 * procedure: moveCursor
 * description: moves the hardware cursor to the 
 * 	position after the text in the console
*/
static void moveCursor(void)
{
	unsigned short temp;

	temp = (curr_vc->csr_y * COLS + curr_vc->csr_x) + (curr_vc->fb_adr - vidmem);
	
	// send special commands to the video controller
	outportb(0x3D4, 14);
	outportb(0x3D5, temp >> 8);
	outportb(0x3D4, 15);
	outportb(0x3D5, temp);
}

/*
 * procedure: scroll
 * description: move the lines in the console up one, i.e. "scroll"
*/
static void scroll()
{
	unsigned space, temp;
	unsigned short *addr = curr_vc->fb_adr; // easier to type 'addr' :P
	
	space = 0x20 | ((unsigned)curr_vc->attrib << 8);
	
	// copy the lines up and replace the last line with spaces
	if(curr_vc->csr_y >= ROWS)
	{
		// copy all but the first line up one
		temp = curr_vc->csr_y - ROWS + 1;
		memcpy(addr, addr + temp * COLS, (ROWS - temp) * COLS * 2);
		
		// clear the last line
		memsetw(addr + (ROWS - temp) * COLS, space, COLS);
		curr_vc->csr_y = ROWS-1;
	}
	moveCursor();
}

void get_cursor(int *x, int *y)
{
	*x = curr_vc->csr_x;
	*y = curr_vc->csr_y;
}

/*
 * procedure: move_up
 * description: move the cursor up one row
 */
void move_up()
{
	if(curr_vc->csr_y > 0 && curr_vc->csr_y < ROWS)
	{
		curr_vc->csr_y--;
		moveCursor();
	}
}


/*
 * procedure: move_left
 * description: move the cursor left
 */
void move_left()
{
	if(curr_vc->csr_x > 0 && curr_vc->csr_x < COLS)
	{
		curr_vc->csr_x--;
		moveCursor();
	}
}

/*
 * procedure: move_right
 * description: move the cursor right
 */
void move_right()
{
	if(curr_vc->csr_x >= 0 && curr_vc->csr_x < COLS - 1)
	{
		curr_vc->csr_x++;
		moveCursor();
	}
}

/*
 * procedure: move_down
 * description: moves the cursor down
 */
void move_down()
{
	if(curr_vc->csr_y >= 0 && curr_vc->csr_y < ROWS - 1)
	{
		curr_vc->csr_y++;
		moveCursor();
	}
}

/*
 * procedure: cls
 * description: clears the screen
*/
void cls()
{
	unsigned blank;
	int i;
	unsigned short *adr = curr_vc->fb_adr;
	
	blank = 0x20 | ((unsigned)curr_vc->attrib << 8);
	
	for(i = 0; i < ROWS; i++)
		memsetw(adr + i * COLS, blank, COLS);
	
	// reset the cursor to the top-left
	curr_vc->csr_x = 0;
	curr_vc->csr_y = 0;
	moveCursor();
}

/*
 * procedure: putch
 * description: prints a character to the string
*/
void putch(char c)
{
	unsigned short *where;
	unsigned att = (unsigned)curr_vc->attrib << 8;	// move it up to the top nybble
	
	if(c == 0x08)	// backspace
	{
		if(curr_vc->csr_x != 0) 
			curr_vc->csr_x--;
		else if(curr_vc->csr_x == 0 && curr_vc->csr_y > 0)
		{
			curr_vc->csr_y--;
			curr_vc->csr_x = COLS - 1;
			moveCursor();
			return;
		}
	}
	else if(c == 0x09)
	{
		curr_vc->csr_x = (curr_vc->csr_x + 8) & ~(8 - 1);
	}
	else if(c == '\r')	// carriage return
	{
		curr_vc->csr_x = 0;
	}
	else if(c == '\n') // newline + carriage return
	{
		curr_vc->csr_x = 0;
		curr_vc->csr_y++;
	}
	else if(c >= ' ') // space and up are printable characters!
	{
		where = curr_vc->fb_adr + (curr_vc->csr_y * COLS + curr_vc->csr_x);
		*where = c | att;
		curr_vc->csr_x++;
	}
	
	if(curr_vc->csr_x >= COLS) // wrap around to the next line
	{
		curr_vc->csr_x = 0;
		curr_vc->csr_y++;
	}
	
	scroll();
}

char getch()
{
	char c = curr_vc->keystrokes.data[0];
	curr_vc->keystrokes.data[0] = (char)0;
	return c;
}

/*
 * procedure: puts
 * description: prints a string to the screen
*/
void puts(char *text)
{
	int i;
	
	for(i = 0; i < strlen(text); i++)
	{
		putch(text[i]);
	}
}

/*
 * procedure: settextcolor
 * description: sets the text color of the current console
*/
void settextcolor(unsigned char forecolor, unsigned char backcolor)
{
	curr_vc->attrib = (backcolor << 4) | (forecolor & 0x0F);
}

/*
 * procedure: switchConsole
 * description: switch virtual consoles
*/
void switchConsole(unsigned which)
{
	unsigned i;
	
	if(which >= NUM_VC) return;

	curr_vc = &vcs[which];
	i = curr_vc->fb_adr - vidmem;
	outportb(0x3D4, 12);
	outportb(0x3D5, i >> 8);
	outportb(0x3D4, 13);
	outportb(0x3D5, i);
	moveCursor();
}

/*
 * procedure: initVideo
 * description: initialize this video driver
*/
void initVideo(void)
{
	unsigned int i;
	vidmem = (unsigned short*)0xB8000;
	for(i = 0; i < NUM_VC; i++)
	{
		curr_vc = &vcs[i];
		curr_vc->attrib = 0x0F;
		curr_vc->fb_adr = vidmem + COLS * ROWS * i;
		cls();
	}
	switchConsole(0);
}

